﻿using System;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using GraphSharp.Controls;
using Microsoft.Practices.EnterpriseLibrary.Logging;
using XmlStudio.Helper;
using System.Windows.Media;

namespace XmlStudio.Controls {
    public static class DesignerCanvasFinder {
        public static DesignerCanvas FindParentCanvas(DependencyObject child) {
            var parent = VisualTreeHelper.GetParent(child);
            while(parent.GetType() != typeof(DesignerCanvas)) {
                parent = VisualTreeHelper.GetParent(parent);
                if(parent is Window) {
                    return null;
                }
            }

            return parent as DesignerCanvas;
        }
    }

    public static class DragBehaviour {
        #region Fields
        private const string LogCategory = "DragBehaviour";
        #endregion

        #region Attached DPs
        public static readonly DependencyProperty IsDragEnabledProperty = DependencyProperty.RegisterAttached("IsDragEnabled", typeof(bool), typeof(DragBehaviour), new UIPropertyMetadata(false, OnIsDragEnabledPropertyChanged));
        public static readonly DependencyProperty IsDraggingProperty = DependencyProperty.RegisterAttached("IsDragging", typeof(bool), typeof(DragBehaviour), new UIPropertyMetadata(false));
        public static readonly DependencyProperty XProperty = DependencyProperty.RegisterAttached("X", typeof(double), typeof(DragBehaviour), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
        public static readonly DependencyProperty YProperty = DependencyProperty.RegisterAttached("Y", typeof(double), typeof(DragBehaviour), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
        private static readonly DependencyPropertyKey OriginalXPropertyKey = DependencyProperty.RegisterAttachedReadOnly("OriginalX", typeof(double), typeof(DragBehaviour), new UIPropertyMetadata(0.0));
        private static readonly DependencyPropertyKey OriginalYPropertyKey = DependencyProperty.RegisterAttachedReadOnly("OriginalY", typeof(double), typeof(DragBehaviour), new UIPropertyMetadata(0.0));
        #endregion

        #region Get/Set method for Attached Properties
        public static bool GetIsDragEnabled(DependencyObject obj) {
            return (bool)obj.GetValue(IsDragEnabledProperty);
        }

        public static void SetIsDragEnabled(DependencyObject obj, bool value) {
            obj.SetValue(IsDragEnabledProperty, value);
        }

        public static bool GetIsDragging(DependencyObject obj) {
            return (bool)obj.GetValue(IsDraggingProperty);
        }

        public static void SetIsDragging(DependencyObject obj, bool value) {
            obj.SetValue(IsDraggingProperty, value);
        }

        public static double GetX(DependencyObject obj) {
            return (double)obj.GetValue(XProperty);
        }

        public static void SetX(DependencyObject obj, double value) {
            obj.SetValue(XProperty, value);
        }

        public static double GetY(DependencyObject obj) {
            return (double)obj.GetValue(YProperty);
        }

        public static void SetY(DependencyObject obj, double value) {
            obj.SetValue(YProperty, value);
        }

        private static double GetOriginalX(DependencyObject obj) {
            return (double)obj.GetValue(OriginalXPropertyKey.DependencyProperty);
        }

        private static void SetOriginalX(DependencyObject obj, double value) {
            obj.SetValue(OriginalXPropertyKey, value);
        }

        private static double GetOriginalY(DependencyObject obj) {
            return (double)obj.GetValue(OriginalYPropertyKey.DependencyProperty);
        }

        private static void SetOriginalY(DependencyObject obj, double value) {
            obj.SetValue(OriginalYPropertyKey, value);
        }
        #endregion

        #region PropertyChanged callbacks
        private static void OnIsDragEnabledPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
            var element = obj as FrameworkElement;
            FrameworkContentElement contentElement = null;
            if(element == null) {
                contentElement = obj as FrameworkContentElement;
                if(contentElement == null)
                    return;
            }

            if(e.NewValue is bool == false)
                return;

            if((bool)e.NewValue) {
                //register the event handlers
                if(element != null) {
                    //registering on the FrameworkElement
                    element.MouseLeftButtonDown += OnDragStarted;
                    element.MouseLeftButtonUp += OnDragFinished;
                } else {
                    //registering on the FrameworkContentElement
                    contentElement.MouseLeftButtonDown += OnDragStarted;
                    contentElement.MouseLeftButtonUp += OnDragFinished;
                }
                //Debug.WriteLine("DragBehaviour registered.", "WPFExt");
            } else {
                //unregister the event handlers
                if(element != null) {
                    //unregistering on the FrameworkElement
                    element.MouseLeftButtonDown -= OnDragStarted;
                    element.MouseLeftButtonUp -= OnDragFinished;
                } else {
                    //unregistering on the FrameworkContentElement
                    contentElement.MouseLeftButtonDown -= OnDragStarted;
                    contentElement.MouseLeftButtonUp -= OnDragFinished;
                }
                //Debug.WriteLine("DragBehaviour unregistered.", "WPFExt");
            }
        }
        #endregion

        private static void OnDragStarted(object sender, System.Windows.Input.MouseButtonEventArgs e) {
            var vertices = SelectionService.GetCurrentInstance(sender as DependencyObject).CurrentSelection.OfType<VertexControl>();

            foreach(var vertex in vertices) {
                var obj = vertex as DependencyObject;
                //we are starting the drag
                SetIsDragging(obj, true);

                Point pos = e.GetPosition(obj as IInputElement);

                //save the position of the mouse to the start position
                SetOriginalX(obj, pos.X);
                SetOriginalY(obj, pos.Y);
                // Debug.WriteLine(string.Format("Drag started on object: {0}, at position: {1}, {2}", obj, pos.X, pos.Y));
            }

            //capture the mouse
            var element = sender as FrameworkElement;
            if(element != null) {
                element.CaptureMouse();
                element.MouseMove += OnDragging;
            } else {
                var contentElement = sender as FrameworkContentElement;
                if(contentElement == null)
                    throw new ArgumentException("The control must be a descendent of the FrameworkElement or FrameworkContentElement!");
                contentElement.CaptureMouse();
                contentElement.MouseMove += OnDragging;
            }

            e.Handled = true;
        }

        private static void OnDragFinished(object sender, System.Windows.Input.MouseButtonEventArgs e) {
            DependencyObject obj;

            var vertices = SelectionService.GetCurrentInstance(sender as DependencyObject).CurrentSelection.OfType<VertexControl>();

            foreach(var vertex in vertices) {
                obj = vertex;
                SetIsDragging(obj, false);
                obj.ClearValue(OriginalXPropertyKey);
                obj.ClearValue(OriginalYPropertyKey);
            }

            obj = (DependencyObject)sender;
            // e.GetPosition(obj as IInputElement);
            // Debug.WriteLine(string.Format("Drag finished on object: {0}, at position: {1}, {2}", obj, pos.X, pos.Y));

            //we finished the drag, release the mouse
            var element = obj as FrameworkElement;
            if(element != null) {
                element.MouseMove -= OnDragging;
                element.ReleaseMouseCapture();
            } else {
                var contentElement = sender as FrameworkContentElement;
                if(contentElement == null)
                    throw new ArgumentException("The control must be a descendent of the FrameworkElement or FrameworkContentElement!");
                contentElement.MouseMove -= OnDragging;
                contentElement.ReleaseMouseCapture();
            }

            e.Handled = true;
        }

        private static void OnDragging(object sender, System.Windows.Input.MouseEventArgs e) {
            var obj = sender as DependencyObject;
            if(!GetIsDragging(obj))
                return;

            var vertices = SelectionService.GetCurrentInstance(sender as DependencyObject).CurrentSelection.OfType<VertexControl>();

            var left = (from x in vertices orderby GetX(x) select x).FirstOrDefault();
            var top = (from y in vertices orderby GetY(y) select y).FirstOrDefault();

            if(left != null && top != null) {
                var mostLeftPosition = new Point(GetX(left), GetY(left));
                var mostTopPosition = new Point(GetX(top), GetY(top));

                //Logger.Writer.Write(string.Format("Selected vertexes: {0}", vertices.Count()), LogCategory, 10, 1, TraceEventType.Verbose, "");
                //Logger.Writer.Write(string.Format("MostLeftPosition: {0}, {1}", mostLeftPosition.X, mostLeftPosition.Y), LogCategory, 10, 1, TraceEventType.Verbose, "");
                //Logger.Writer.Write(string.Format("MostTopPosition: {0}, {1}", mostTopPosition.X, mostTopPosition.Y), LogCategory, 10, 1, TraceEventType.Verbose, "");

                // ak je viac vybranych, tak viac vpravo a dole neposuvat az za hranice podla left, ale podla rozdielu medzi aktualnym prvkom a najlavejsim (najvrchnejsim)
                foreach(var vertex in vertices) {
                    obj = vertex;
                    Point pos = e.GetPosition(vertex);
                    double horizontalChange = pos.X - GetOriginalX(vertex);
                    double verticalChange = pos.Y - GetOriginalY(vertex);

                    double distanceFromMostLeft = GetX(vertex) - mostLeftPosition.X;
                    double distanceFromMostTop = GetY(vertex) - mostTopPosition.Y;

                    if(double.IsNaN(GetX(obj)))
                        SetX(obj, 0);
                    if(double.IsNaN(GetY(obj)))
                        SetY(obj, 0);

                    //move the object
                    var newX = GetX(obj) + horizontalChange;
                    var newY = GetY(obj) + verticalChange;
                    if(left != null)
                        if(newX >= left.DesiredSize.Width * 0.5 + distanceFromMostLeft) {
                            SetX(obj, newX);
                        }

                    if(top != null)
                        if(newY >= top.DesiredSize.Height * 0.5 + distanceFromMostTop) {
                            SetY(obj, newY);
                        }
                }

                e.Handled = true;
            }
        }
    }
}
